package cn.yuli.redis.aop;

import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.annotation.PostConstruct;

import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import com.google.common.base.Charsets;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;

import cn.yuli.redis.annotation.CacheResult;
import cn.yuli.redis.mapper.UserMapper;
import cn.yuli.redis.model.User;
import cn.yuli.redis.service.CacheService2;
import cn.yuli.redis.utils.SpelParser;

@Aspect
@Component
public class CacheAspect {
	public static Logger logger = Logger.getLogger(CacheAspect.class);
	//互斥锁，用来缓解缓存雪崩
	private Lock lock = new ReentrantLock();
	//缓存服务类
	@Autowired
	private CacheService2 cs;
	//布隆过滤器，用来防止缓存击穿
	private BloomFilter<String> bf;
	@Autowired
	private UserMapper mapper;
	@PostConstruct
	public void init(){
		//初始化布隆过滤器，使用id来作为过滤名单
		List<String> userIds = mapper.selectAllUserId();
		bf = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8),userIds.size());
		for (String userId : userIds) {
			bf.put(userId);
		}
	}
	/**
	 * 缓存通知
	 * @param point
	 * @param cr
	 * @return
	 * @throws Throwable
	 */
	@Around("@annotation(cr)")
	public Object doAround(ProceedingJoinPoint point,CacheResult cr) throws Throwable{
		String key = getKey(cr.key(), point);//键名
		System.out.println(key);
		String buckupkey = cr.backupKey();//备份的键名
		String cacheName = cr.cacheName();//缓存域的名称
		boolean needBloomFilter = cr.needBloomFilter();//是否使用布隆过滤器
		boolean needLock = cr.needLock();//是否枷锁
		//使用布隆过滤器来校验数据
		if(needBloomFilter && !bf.mightContain(key)){
			logger.info("id名不存在于名单中");
			return null;
		}
		User rUser = cs.cacheResult(key, cacheName);
		if(rUser != null){
			logger.info("从缓存中取得数据");
			return rUser;
		}
		//缓存中没有数据，仅允许一个任务去数据库中加载数据，并写入缓存
		if(needLock){
			//尝试获取锁，没获取到就从备份缓存中中获取数据
			if(lock.tryLock()){
				Object object = point.proceed();
				cs.cachePut(key, object, cacheName);
				//放一份到备份缓存中
				cs.cachePut(buckupkey+key, object, buckupkey);
				lock.unlock();
				return object;
			}else{
				logger.info("从备份中获取数据");
				return cs.cacheResult(buckupkey+key, buckupkey);
			}
			
		}else{
			Object object = point.proceed();
			cs.cachePut(key, object, cacheName);
			//放一份到备份缓存中
			cs.cachePut(buckupkey+key, object, buckupkey);
			return object;
		}
		
	}
	/**
	 * 解析spring的el表达式方法
	 * @param key
	 * @param point
	 * @return
	 */
	private String getKey(String key,ProceedingJoinPoint point){
		Object[] args = point.getArgs();//获取参数的值
		Signature signature = point.getSignature();//从连接点获取数字签名
		MethodSignature methodSignature = (MethodSignature)signature;
		Method method = methodSignature.getMethod();//获取形参的名字
		String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(method);
		
		return SpelParser.getKey(key, parameterNames, args);
	}
}
